#ifndef BAG_OF_TASKS_H// Use a unique identifier for every file.
#define BAG_OF_TASKS_H

#include "stdafx.h"
#include "Maths.h"
#include <iostream>
#include <pthread.h>
#include <pthread.h>
#include <vector>
using namespace std;

template<typename T>
class RecursiveRayTracingTask
{
public:
	vector<Sphere<T>*> sphereListPointer;
	long startRange;
	long endRange;
	Vec3<T> *outputColection;
	int startingY;
	int startingX;
	int width;
	int height;
	bool *complitedArray;
	int index;
	
	RecursiveRayTracingTask(long startRang, long endRang,vector<Sphere<T>*> spheres, Vec3<T> *outputCol, bool *boolArray, int threadIndex) 
	{
		this->startRange = startRang;
		this->endRange = endRang;
		this->sphereListPointer = spheres;
		this->outputColection = outputCol;
		this->complitedArray = boolArray;
		this->index = threadIndex;
	}
};


template <typename T>
class BagOfTasksReply
{
public:
	void(*backWriter) (void *);
	bool *threadsComplete;
	Vec3<T> *outputResult;
	int width;
	int height;
	int amount;
	vector<Sphere<T>*> spheres;
};


template <typename T>
class BagOfTasks
{

public:
	
	int amountOfThreads;
	//workers who will execute the job	
	pthread_t tasks[12];
	//the worker who will be incharge of calling back the safe to file function
	pthread_t backWriterThread;
	Vec3<T> *outputResult;
	vector<Sphere<T>*> sphereCollection;
	void (*backWriter) (void *);
	void (*executer)(void *);
	int amountPerThread;
	bool threadsComplete[12];

	int width; 
	int height;

	BagOfTasks(vector<Sphere<T>*> sphereCollection, int width, int height)
	{
		this->amountOfThreads = 12;
		//total amount of rays 
		this->height = height;
		this->width = width;
		int total = height * width;
		this->amountPerThread = total/this->amountOfThreads;
		this->sphereCollection = sphereCollection;
		this->outputResult = new Vec3<T>[width * height];
	}


	static bool VerifyThreadsComplete(int amount,bool *threadsComplete )
	{
		for(int index = 0; index < amount ;index ++)
		{
			if(threadsComplete[index] == false)
			{
				return false;
			}
		}
		return true;
	}

	static void *WaitNReturnValue(void *replyStruct)
	{
		BagOfTasksReply<T> *reply = (BagOfTasksReply<T>*)replyStruct;

		while(BagOfTasks<T>::VerifyThreadsComplete(reply->amount,reply->threadsComplete) == false)
		{
			// wait here untill all threads are finished
		}
		
		RecursiveRayTracer<T>::ReturnedOutput(reply);
		return NULL;
	}

	void execute()
	{
		for(int index = 0; index < this->amountOfThreads; index ++)
		{
			threadsComplete[index] = false;
		}
		vector<RecursiveRayTracingTask<T> *> tasksStructCollection;
		//(int startRang, int endRang, Vec3<Sphere<T>*> *spheres, Vec3<T> *outputCol) : startRange(startRang), endRange(endRang), outputColection(outputCol), sphereListPointer(spheres){ }

		int currentStartingPoint = 0;
		int lastEndX = 0;
		int lastEndY = 0;
		for(int taskIndex = 0; taskIndex < amountOfThreads; taskIndex++)
		{
			int currentEndPoint = currentStartingPoint + this->amountPerThread - 1;
			RecursiveRayTracingTask<T> *currentTask = new RecursiveRayTracingTask<T>
				(currentStartingPoint, currentEndPoint, this->sphereCollection, this->outputResult, this->threadsComplete, taskIndex);
			currentTask->width = this->width;
			currentTask->height = this->height;
			if(currentStartingPoint == 0)
			{
				currentTask->startingX = 0;
				currentTask->startingY = 0;

				if(amountPerThread % this->width == 0)
				{
					lastEndY = amountPerThread/this->width;
					lastEndX = 0;
				}
				else
				{
					lastEndY = amountPerThread/this->width - 1;
					int remainingX = amountPerThread - (lastEndY + 1 * width);
					lastEndX = remainingX - 1;
				}
			}
			else
			{

				if(amountPerThread % this->width == 0)
				{
					currentTask->startingX = 0;
					currentTask->startingY = lastEndY;
					lastEndY += amountPerThread/this->width;
					lastEndX = 0;
				}
				else
				{
					currentTask->startingX = lastEndX + 1;
					currentTask->startingY = lastEndY;

					int remaningToWidth = this->width - lastEndX + 1;

					int remainingAmount  = amountPerThread - remaningToWidth;

					if(remainingAmount % width == 0)
					{
						lastEndY += remainingAmount /width;
						lastEndX = 0;
					}
					else
					{
						int amounrOfRows =  remainingAmount /width;
						lastEndY +=amounrOfRows;
						lastEndX = remainingAmount - (amounrOfRows * width) -1;
					}
				}
			}
			tasksStructCollection.push_back(currentTask);
			currentStartingPoint = currentEndPoint + 1;
		}

		for(int threadIndex = 0; threadIndex < amountOfThreads; threadIndex++)
		{
			RecursiveRayTracingTask<T> *currentTask = tasksStructCollection[threadIndex];
			pthread_create(&tasks[threadIndex], NULL, RecursiveRayTracer<T>::ThreadExecute, currentTask);
			
		}

		BagOfTasksReply<T>* newReply = new BagOfTasksReply<T>();
		newReply->backWriter = this->backWriter;
		newReply->outputResult = this->outputResult;
		newReply->amount = this->amountOfThreads;
		newReply->threadsComplete = this->threadsComplete;
		newReply->width = this->width;
		newReply->height = this->height;
		newReply->spheres = this->sphereCollection;

		pthread_create(&backWriterThread, NULL,BagOfTasks<T>::WaitNReturnValue, newReply);
	}
};
#endif